﻿using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Reflection;
using System.Windows.Media.Imaging;

namespace SSG.Tools
{
    /// <summary>
    ///   Misc. helper methods.
    /// </summary>
    public static class GraphicTools
    {
        #region Icon / Images / Bitmap
        /// <summary>
        ///   Converts an image into an icon.
        /// </summary>
        /// <param name = "image">The image that shall become an icon</param>
        /// <param name = "size">The width and height of the icon. Standard
        ///   sizes are 16x16, 32x32, 48x48, 64x64.</param>
        /// <param name = "keepAspectRatio">Whether the image should be squashed into a
        ///   square or whether whitespace should be put around it.</param>
        /// <returns>An icon!!</returns>
        public static Icon MakeIcon(this Image image, int size, bool keepAspectRatio)
        {
            if (image == null) return null;
            Bitmap square = image.ResizeImage2Bitmap(size, keepAspectRatio);

            // following line would work directly on any image, but then
            // it wouldn't look as nice.
            return Icon.FromHandle(square.GetHicon());
        }

        /// <summary>
        ///   Resize an image to <paramref name="size"/> pixels.
        /// </summary>
        /// <param name = "image"></param>
        /// <param name = "size"></param>
        /// <param name = "keepAspectRatio"></param>
        /// <returns></returns>
        public static Image ResizeImage(this Image image, int size, bool keepAspectRatio)
        {
            if (image == null) return null;
            Bitmap square = image.ResizeImage2Bitmap(size, keepAspectRatio);
            return square;
        }

        /// <summary>
        ///   Resize an image.
        /// </summary>
        /// <param name = "image"></param>
        /// <param name = "size"></param>
        /// <param name = "keepAspectRatio"></param>
        /// <returns></returns>
        private static Bitmap ResizeImage2Bitmap(this Image image, int size, bool keepAspectRatio)
        {
            Bitmap square = new Bitmap(size, size); // create new bitmap
            global::System.Drawing.Graphics g = global::System.Drawing.Graphics.FromImage(square); // allow drawing to it

            int x, y, w, h; // dimensions for new image

            if (!keepAspectRatio || image.Height == image.Width) {
                // just fill the square
                x = y = 0; // set x and y to 0
                w = h = size; // set width and height to size
            }
            else {
                // work out the aspect ratio
                // ReSharper disable RedundantCast
                float r = (float) image.Width/(float) image.Height;
                // ReSharper restore RedundantCast

                // set dimensions accordingly to fit inside size^2 square
                if (r > 1) {
                    // w is bigger, so divide h by r
                    w = size;
                    h = (int) (size/r);
                    x = 0;
                    y = (size - h)/2; // center the image
                }
                else {
                    // h is bigger, so multiply w by r
                    w = (int) (size*r);
                    h = size;
                    y = 0;
                    x = (size - w)/2; // center the image
                }
            }

            // make the image shrink nicely by using HighQualityBicubic mode
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.DrawImage(image, x, y, w, h); // draw image with specified dimensions
            g.Flush(); // make sure all drawing operations complete before we get the icon

            return square;
        }

        /// <summary>
        ///   Return image from data in a byte array.
        /// </summary>
        /// <param name = "data"></param>
        /// <returns></returns>
        public static Image GetImage(byte[] data)
        {
            if (data == null) return null;
            if (data.Length < 1) return null;
            MemoryStream ms = new MemoryStream(data);
            Image img = Image.FromStream(ms);
            // closing the stream later crashes the application
            // ms.Close(); // => memory leak
            return img.FixRawFormat();
        }

        /// <summary>
        ///   Bytes (raw data) of <paramref name = "bitmap" />.
        /// </summary>
        /// <param name = "bitmap"></param>
        /// <returns></returns>
        public static byte[] GetBytes(this Bitmap bitmap)
        {
            if (bitmap == null) return null;
            MemoryStream ms = new MemoryStream();
            bitmap.Save(ms, ImageFormat.Bmp);
            ms.Flush();
            return ms.ToArray();
        }

        /// <summary>
        ///   Return an icon (based on its resource name
        /// </summary>
        /// <param name = "imageName"></param>
        /// <returns></returns>
        public static Icon GetIcon16(string imageName)
        {
            // check 
            if (String.IsNullOrEmpty(imageName)) return null;

            Image image = GraphicTools.GetImage(imageName);
            return image == null ? null : image.MakeIcon(16, true);
        }

        /// <summary>
        ///   Get an image .
        /// </summary>
        /// <param name = "imageName"></param>
        /// <returns></returns>
        public static Image GetImage(string imageName)
        {
            // check 
            if (String.IsNullOrEmpty(imageName)) return null;

            // init
            Stream stream = null;
            Image img = null;
            string resName = GraphicTools.GetImageResourceName(imageName);

            // resource?
            if (String.IsNullOrEmpty(resName)) return null;
            try {
                stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resName);
                if (stream != null) img = Image.FromStream(stream);
                Debug.Assert(img != null, "Image not loaded, img: " + imageName + " / res: " + resName);
            }
            finally {
                if (stream != null) stream.Close();
            }
            return img;
        }

        /// <summary>
        ///   Image from bitmap.
        /// </summary>
        /// <param name = "bitmap"></param>
        /// <returns></returns>
        public static Image GetImage(this Bitmap bitmap)
        {
            return bitmap == null ? null : Image.FromHbitmap(bitmap.GetHbitmap());
        }

        /// <summary>
        /// Bitmap to bitmap source
        /// </summary>
        /// <param name="bitmap"></param>
        /// <returns></returns>
        public static BitmapImage GetBitmapImage(this Bitmap bitmap)
        {
            // http://stackoverflow.com/a/6775114/356726
            if (bitmap == null) return null;

            BitmapImage result;
            using (MemoryStream stream = new MemoryStream()) {
                bitmap.Save(stream, ImageFormat.Png); // PNG preserves transparency
                stream.Position = 0;
                result = new BitmapImage();
                result.BeginInit();
                // According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed."
                // Force the bitmap to load right now so we can dispose the stream.
                result.CacheOption = BitmapCacheOption.OnLoad;
                result.StreamSource = stream;
                result.EndInit();
                result.Freeze();
            }
            return result;
        }

        /// <summary>
        ///   Fixes issues with the <see cref = "Image.RawFormat" /> type in order to properly 
        ///   serialize the image.
        /// </summary>
        /// <param name = "image"></param>
        /// <returns></returns>
        public static Image FixRawFormat(this Image image)
        {
            if (image == null) return null;

            // ReSharper disable ConditionIsAlwaysTrueOrFalse
            if (image is Bitmap && image.RawFormat != null && image.RawFormat.Equals(ImageFormat.MemoryBmp)) {
                Bitmap bm = image as Bitmap;
                ImageFormat format = ImageFormat.Bmp;
                MemoryStream str = new MemoryStream();
                bm.Save(str, format);
                return new Bitmap(str);
            }
            // ReSharper restore ConditionIsAlwaysTrueOrFalse

            return image;
        }

        /// <summary>
        ///   Find the "real" resource name
        /// </summary>
        /// <param name = "imageName"></param>
        /// <returns></returns>
        private static string GetImageResourceName(string imageName)
        {
            if (String.IsNullOrEmpty(imageName)) return null;
            Assembly thisAssembly = Assembly.GetExecutingAssembly();
            string[] res = thisAssembly.GetManifestResourceNames();
            string s = imageName.ToUpper();
            string spng = s + ".PNG";
            string sgif = s + ".GIF";
            string sico = s + ".ICO";

            // ReSharper disable LoopCanBeConvertedToQuery
            foreach (string t in res) {
                string r = t.ToUpper();
                if (r.EndsWith(s) || r.EndsWith(spng) || r.EndsWith(sgif) || r.EndsWith(sico))
                    return t; // the original name
            }
            // ReSharper restore LoopCanBeConvertedToQuery
            return null;
        }
        #endregion Icon / Images / Bitmap

        #region Colour
        /// <summary>
        /// To ARGB Hex value
        /// </summary>
        /// <param name="colour"></param>
        /// <returns></returns>
        public static string ToArgbHex(this System.Windows.Media.Color colour)
        {
            // http://stackoverflow.com/questions/3315088/how-do-i-serialize-a-system-windows-media-color-object-to-an-srgb-string
            return string.Format("#{0:X2}{1:X2}{2:X2}{3:X2}", colour.A, colour.R, colour.G, colour.B);
        }

        /// <summary>
        /// Set a new opacity value for a given ARGB string
        /// </summary>
        /// <param name="argb"></param>
        /// <param name="opacity"></param>
        /// <returns></returns>
        public static string SetOpacityForArgbHex(this string argb, int opacity)
        {
            if (String.IsNullOrEmpty(argb)) throw new ArgumentNullException("argb");
            if (opacity < 0 || opacity > 100) throw new ArgumentOutOfRangeException("opacity");
            
            // opacity is 0-100, we need 00 to FF (255)
            opacity = (int) (Math.Round(2.55*opacity));
            if (opacity > 255) opacity = 255;
            string opacityHex = opacity.ToString("X2");
            
            // build ARGB string
            bool withHash = argb.StartsWith("#");
            string newArgb = argb.Replace("#", String.Empty).Substring(2);
            newArgb = opacityHex + newArgb;
            return withHash ? "#" + newArgb : newArgb;
        }

        /// <summary>
        /// From ARGB #aarrggbb string
        /// </summary>
        /// <param name="hexValue"></param>
        /// <returns></returns>
        public static System.Windows.Media.Color FromArgbHex(this String hexValue)
        {
            if (String.IsNullOrEmpty(hexValue)) throw new ArgumentNullException("hexValue");
            string hv = hexValue.Replace("#", String.Empty);
            if (hv.Length != 8) throw new ArgumentException("Expect (#)aarrggbb");
            byte alpha = byte.Parse(hv.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
            byte red = byte.Parse(hv.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
            byte green = byte.Parse(hv.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
            byte blue = byte.Parse(hv.Substring(6, 2), System.Globalization.NumberStyles.HexNumber);
            return System.Windows.Media.Color.FromArgb(alpha, red, green, blue);
        }

        /// <summary>
        /// ARGB to ABGR string
        /// </summary>
        /// <param name="argbHex"></param>
        /// <returns></returns>
        public static string ArgbToAbgrHex(this string argbHex)
        {
            if (String.IsNullOrEmpty(argbHex)) throw new ArgumentNullException("argbHex");
            bool hashed = argbHex.StartsWith("#");
            if (hashed) argbHex = argbHex.Substring(1);
            if (argbHex.Length != 8) throw new ArgumentException("argbHex");
            string a = argbHex.Substring(0, 2);
            string r = argbHex.Substring(2, 2);
            string g = argbHex.Substring(4, 2);
            string b = argbHex.Substring(6, 2);
            string abgr = a + b + g + r;
            return hashed ? "#" + abgr : abgr;
        }
        #endregion Colour
    }
}